Vamos a partir de lo que hemos visto en el notebook anterior...
In [ ]:
# primero hacemos los imports de turno
import os
import datetime as dt
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(19760812)
%matplotlib inline
In [ ]:
# Leemos los datos del fichero 'mast.txt'
ipath = os.path.join('Datos', 'mast.txt')
def dateparse(date, time):
YY = 2000 + int(date[:2])
MM = int(date[2:4])
DD = int(date[4:])
hh = int(time[:2])
mm = int(time[2:])
return dt.datetime(YY, MM, DD, hh, mm, 0)
cols = ['Date', 'time', 'wspd', 'wspd_max', 'wdir',
'x1', 'x2', 'x3', 'x4', 'x5',
'wspd_std']
wind = pd.read_csv(ipath, sep = "\s*", names = cols,
parse_dates = [[0, 1]], index_col = 0,
date_parser = dateparse)
In [ ]:
wind.info()
In [ ]:
wind.describe()
Acceso a los índices, valores, columnas (las Series
no disponen de este atributo):
In [ ]:
wind.index
In [ ]:
wind.values
In [ ]:
wind.values.shape
In [ ]:
wind.columns
Hay varias columnas que no nos interesan (las que hemos llamado x1, x2, x3, x4 y x5). Vamos a eliminarlas de nuestro DataFrame
. Lo podemos hacer de varias formas:
In [ ]:
# eliminamos una columna usando la keyword 'del'
del wind['x1']
wind.head(3)
In [ ]:
# Extraemos una columna usando el método pop
s = wind.pop('x2')
wind.head(3)
In [ ]:
del wind['x3']
del wind['x4']
del wind['x5']
In [ ]:
wind.info()
Una de las columnas, la que hemos extraído con el método pop
, la hemos almacenado en la variable s
, que es una Series
:
In [ ]:
type(s)
In [ ]:
s.head(3)
In [ ]:
s.info()
¿Es una TimeSeries?, i.e., ¿todos los índices son fechas?
In [ ]:
# s.is_time_series deprecated
s.index.is_all_dates
In [ ]:
s.describe()
In [ ]:
s.dtype
In [ ]:
s.values
In [ ]:
s.index
In [ ]:
s.columns
In [ ]:
# Creamos un DataFrame
df = pd.DataFrame(np.array([['a','b','c','d','e'], [10,20,30,40,50]]).T,
columns = ['col1', 'col2'])
df
Los índices se pueden reescribir en cualquier momento:
In [ ]:
df.index = np.arange(1,6) * 100
df
Podemos usar una columna para definir nuestros índices:
In [ ]:
df.set_index('col1', inplace = True)
df
Podemos deshacer lo anterior usando:
In [ ]:
df.reset_index(inplace = True)
df
También podemos cambiar los nombres de las columnas:
In [ ]:
df.columns = ['column1', 'column2']
df
Podemos darle un nombre a la columna de índices (como hemos visto anteriormente):
In [ ]:
df.index.name = 'indices'
df
No olvidemos que entre bambalinas tenemos numpy arrays y pandas expone mucha de la funcionalidad de los numpy arrays directamente dentro de sus propias estructuras de datos.
Vemos, por ejemplo, qué atributos de un numpy array están disponibles directamente en una Series
(o en un DataFrame
):
In [ ]:
numpy_attrs = dir(s.values)
series_attrs = dir(s)
for attr in numpy_attrs:
if attr not in series_attrs:
print('NOOOOOOOOOOOOOOOOOOOOOO', attr)
else:
print(attr)
Por tanto, muchas operaciones que hacemos con un numpy array la tenemos directamente disponible con una estructura de datos de Pandas:
In [ ]:
s.mean()
In [ ]:
s.min()
In [ ]:
s.max()
In [ ]:
s[0:10].tolist()
...
Nota:
Hay momentos en que resultará conveniente usar directamente los métodos de los numpy arrays si el rendimiento es un problema.
In [ ]:
%%timeit
s.mean()
s.min()
s.max()
In [ ]:
%%timeit
s.values.mean()
s.values.min()
s.values.max()
Sed pacientes!!!!!!
In [ ]:
numpy_attrs = dir(s.values)
series_attrs = dir(s)
for attr in series_attrs:
if attr not in numpy_attrs:
print('NOOOOOOOOOOOOOOOOOOOOOO', attr)
else:
print(attr)
In [ ]:
numpy_attrs = dir(s.values)
dataframe_attrs = dir(wind)
for attr in dataframe_attrs:
if attr not in numpy_attrs:
print('NOOOOOOOOOOOOOOOOOOOOOO', attr)
else:
print(attr)
In [ ]:
wind['wspd'].apply(lambda x: str(x) + ' m/s')
In [ ]:
wind.corr()
In [ ]:
wind.cumsum()
In [ ]:
wind.diff()
De momento, hasta que no veamos como hacer una serie de operaciones, estamos pasando por todo esto por encima. Luego veremos algunas cosas más en detalle...
Hagamos algunos ejemplos sencillos:
In [ ]:
# obtened la velocidad media del viento (columna 'wspd'):
In [ ]:
# obtened la mediana de la dirección del viento (columna 'wdir'):
In [ ]:
# obtened el valor máximo de la diferencia entre dos
# pasos temporales de la desviación estándar de la velocidad (columna 'wspd_std')
Otros métodos interesantes son los pd.rolling_*
:
In [ ]:
pd.rolling_mean(wind, 5, center = True).head(10)
Como podéis leer en el mensaje de error estas funciones rolling_*
dejarán de existir en el futuro. En la celda de texto anterior he hablado de métodos de forma explícita porque todas las funciones rolling_*
se han agrupado en el método rolling
. Veamos lo anterior con la nueva forma:
In [ ]:
wind.rolling(5, center = True).mean().head(10)
Otras cosas a las que les podéis echar un ojo en un DataFrame
(cambiad DataFrame
por Series
o cualquier otra estructura de datos de vuestro interés):
In [ ]:
import inspect
info = inspect.getmembers(wind, predicate=inspect.ismethod)
for stuff in info:
print(stuff[0])
In [ ]:
index = pd.date_range('2000/01/01', freq = '12H', periods = 10)
index = index.append(pd.date_range('2000/01/10', freq = '1D', periods = 3))
df = pd.DataFrame(np.random.randint(1, 100, size = (13, 3)),
index = index, columns = ['col1', 'col2', 'col3'])
df
In [ ]:
# Vamos a rellenar algunos valores con NaN
df[df > 70] = np.nan
df
A diferencia de lo que sucede con un numpy array, en pandas, las operaciones con NaN
ignoran los mismos. Veamos esto en acción:
In [ ]:
df['col1'].sum()
In [ ]:
df['col1'].values.sum()
In [ ]:
df['col1'].sum(skipna = False)
Podemos detectar los valores nulos (NaN
) usando isnull
:
In [ ]:
df.isnull()
O los no nulos usando notnull
:
In [ ]:
df.notnull()
Vemos que tenemos valores NaN
. Estos los podemos rellenar usando ffill
o bfill
(similar a fillna(method = 'ffill')
y a fillna(method = 'bfill')
, respectivamente):
In [ ]:
# Recordemos como era nuestro DataFrame
df
In [ ]:
df.ffill()
In [ ]:
df.bfill()
In [ ]:
df.fillna(value = 'Kiko')
Creamos un nuevo DataFrame
con un frecuencia de 12H en los índices.
In [ ]:
df = pd.DataFrame(np.random.randint(1, 100, size = (15, 3)),
index = pd.date_range('2015/01/01', freq = '12H', periods = 15))
df
In [ ]:
df[df > 70] = 'Kiko'
df
Podemos eliminar las filas o columnas que tengan algún valor NaN
, todos los valores NaN
,...
In [ ]:
df[df == 'Kiko'] = np.nan
df
In [ ]:
# Eliminamos las filas donde algún valor de la fila sea NaN
# axis = 0 sería equivalente a axis = 'rows'
# Más tarede veremos más sobre esto!!!!
df.dropna(axis = 'rows')
In [ ]:
# Eliminamos las filas donde todos los valores de la fila sean NaN
df.iloc[2, :] = np.nan
df.dropna(axis = 'rows', how = 'all') # axis = 0 sería equivalente a axis = 'rows'
In [ ]:
# Eliminamos las columnas donde algún valor de la columna sea NaN
df.dropna(axis = 'columns', how = 'any') # axis = 1 sería equivalente a axis = 'columns'. Más sobre esto después!!!
# how = 'any' es el valor por defecto por lo que no sería necesario añadirlo
In [ ]:
# Vamos a añadir una columna que no tenga NaNs y vamos a hacer a misma operación
df['col4'] = 9999
df.dropna(axis = 'columns', how = 'any')
In [ ]:
# Ahora vamos a añadir una columna donde todos los valores sean NaN
df['col5'] = np.nan
df.dropna(axis = 'columns', how = 'all')
Tambien podemos rellenar los valores NaN usando interpolate
:
In [ ]:
df.interpolate()
Pero que ha pasado!!! ¿Por qué no ha interpolado?
Veamos como son las columnas
In [ ]:
df.info()
Vemos que las columnas 0
, 1
y 2
son del tipo object
y no son un número. Por otro lado, en la columna col4
no hay nada que interpolar puesto que no hay valores perdidos. Por último, la columna col5
son todo NaN
por lo que no se puede inventar la información. Transformemos las tres primeras columnas e interpolemos:
In [ ]:
df[[0, 1, 2]] = df[[0, 1, 2]].astype(np.float)
In [ ]:
df.interpolate()
In [ ]:
# Mirad la ayuda del método interpolate para aprender mejor como usarlo